#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# JiuZero 2025/5/22

import argparse
import os
import sys
from config import config
from lib.core.log import logger
from lib.core.reverse import reverse_main

def int_list(value):
    try:
        values = list(map(int, value.split(',')))
        for v in values:
            if v not in (-1, 0, 1, 2, 3):
                logger.error(f"Invalid option {v} (allowed values: -1-3)")
                sys.exit(0)
        return values
    except ValueError:
        logger.error("The parameter is in the wrong format, please use comma-separated integers (e.g. 1, 2, 3)")
        sys.exit(0)

def str_list(value):
    try:
        values = list(map(str, value.split(',')))
        return values
    except ValueError:
        logger.error("The parameter is malformed, please use comma-separated characters")
        sys.exit(0)

def cmd_line_parser(argv=None):
    """
    This function parses the command line parameters and arguments
    """

    if not argv:
        argv = sys.argv
        
    _ = os.path.basename(argv[0])
    usage = "z0 [command options] [arguments...]"
    parser = argparse.ArgumentParser(prog='Z0SCAN', usage=usage)
    
    subparsers = parser.add_subparsers(
        dest='command',
        title='Commands', 
        metavar='', 
        help=''
        )
    version_parser = subparsers.add_parser('version', help='Show program\'s version number and exit')
    version_parser.set_defaults(version=True)
    scan_parser = subparsers.add_parser('scan', help='Scan command')
    reverse_parser = subparsers.add_parser('reverse', help='Reverse command(Beta)')
    reverse_parser.set_defaults(reverse=True)
    list_parser = subparsers.add_parser('list', help='Make a list of plugins and lists')
    list_parser.set_defaults(list=True)
    console_parser = subparsers.add_parser('console', help='Console command')
    console_parser.set_defaults(console=True)
    dbcmd_parser = subparsers.add_parser('dbcmd', help='Database command')
    dbcmd_parser.set_defaults(dbcmd=True)

    ## z0 scan
    # Proxy options
    proxy = scan_parser.add_argument_group('Proxy', 'Passive Agent Mode Options')
    proxy.add_argument("-s", "--server-addr", dest="server_addr", help="Server addr format:(ip:port) ")
    
    # Target options
    target = scan_parser.add_argument_group('Target', "Options has to be provided to define the target(s)")
    target.add_argument("-u", "--url", dest="url", help="Target URL (e.g. \"http://www.site.com/vuln.php?id=1\")")
    target.add_argument("-f", "--file", dest="url_file", help="Scan multiple targets given in a textual file")
    # target.add_argument("-d", "--data", dest="data_file", help="Scan Data given in a textual file")
    
    # Requests options
    request = scan_parser.add_argument_group("Request", "Network request options")
    request.add_argument("-p", "--proxy", dest="proxy", help="Use a proxy to connect to the target URL,Support http,https,socks5,socks4 eg:http@127.0.0.1:8080 or socks5@127.0.0.1:1080")
    request.add_argument("--timeout", dest="timeout", default=config.TIMEOUT, help="Seconds to wait before timeout connection (Default {})".format(config.TIMEOUT), type=int)
    request.add_argument("--retry", dest="retry", type=int, default=config.RETRY, help="Time out retrials times (Default {})".format(config.RETRY))
    request.add_argument("--random-agent", dest="random_agent", action="store_true", default=False, help="Use randomly selected HTTP User-Agent header value")
    
    # Output options
    output = scan_parser.add_argument_group("Output", "Output options")
    output.add_argument("--html", dest="html", help="When selected, the output will be output to the output directory by default, or you can specify", action='store_true')
    output.add_argument("--json", dest="json", help="The json file is generated by default in the output directory, you can change the path")
    
    # Optimization options
    optimization = scan_parser.add_argument_group("Optimization", "Optimization options")
    optimization.add_argument('-t', "--threads", dest="threads", type=int, default=config.THREADS, help="Max number of concurrent network requests (Default {})".format(config.THREADS))
    optimization.add_argument("-l", "--level", dest="level", type=int, choices=list(range(0, 4)), default=config.LEVEL, help="Different level use different payloads: 0-3 (Default {})".format(config.LEVEL))
    optimization.add_argument("-r", "--risk", dest="risk", type=int_list, default=config.RISK, help="Different risk use different kind of scanners: [0, 1, 2, 3] (Default {})".format(config.RISK))
    optimization.add_argument("-c", '--concise', dest='concise_output', action="store_true", default=False, help="Concise output like w13scan")
    optimization.add_argument("-iw", '--ignore-waf', dest='ignore_waf', action="store_true", default=False, help="Ignore the WAF during detection")
    optimization.add_argument("-if", '--ignore-fingerprint', dest='ignore_fingerprint', action="store_true", default=False, help="Ignore fingerprint element scanning")
    optimization.add_argument("-sc", '--scan-cookie', dest='scan_cookie', action="store_true", default=False, help="Scan cookie during detection")
    optimization.add_argument("-z", "--zmq-port", dest="zmq_port", help="ZeroMQServer port (Default {})".format(config.ZMQ_PORT), default=config.ZMQ_PORT)
    optimization.add_argument('--disload', dest='disload', type=str_list, default=config.DISLOAD, help="Disload some scanners.")
    optimization.add_argument('--load', dest='load', type=str_list, default=config.LOAD, help="Only load scanners")
    optimization.add_argument("--debug", dest="debug", type=int, choices=list(range(1, 4)), help="Show programs's exception: 1-3")

    ## z0 console
    client = console_parser.add_argument_group('Client', 'Client Options')
    client.add_argument("-z", "--zmq-port", dest="zmq_port", help="ZeroMQClient port (Default {})".format(config.ZMQ_PORT), default=config.ZMQ_PORT)

    args = parser.parse_args()
    if not hasattr(args, 'version'):
        args.version = False
    if not hasattr(args, 'reverse'):
        args.reverse = False
    if not hasattr(args, 'list'):
        args.list = False
    if not hasattr(args, 'console'):
        args.console = False
    if not hasattr(args, 'dbcmd'):
        args.dbcmd = False
    dd = args.__dict__
    
    # Check if scan command is used and has required arguments
    if args.command == 'scan' and not any((dd.get("server_addr"), dd.get("url"), dd.get("url_file"))):
        errMsg = "missing a mandatory option (-s, --server-addr, -u, --url, -f, --file, -d, --data). "
        errMsg += "Use -h for basic and -hh for advanced help\n"
        parser.error(errMsg)
        
    # Check version & reverse flag
    if not (dd.get("version") or dd.get("reverse") or dd.get("dbcmd") or dd.get("list") or dd.get("console")) and not dd.get("command"):
        errMsg = "missing a command or option. Use -h for help\n"
        parser.error(errMsg)
        
    return dd